Using eBird trends and status abundance data, can we find a minimum
number of city total checklists required, and a cut off percentage of
checklists recorded, such that we can be confident that any species
recorded on more than that percentage of checklist is present in the
city (given the city has more that the total minimum total checklists
recorded within it).
source('../env.R')
options(warn=-1)
taxonomic_mapping = read_csv(filename(TAXONOMY_OUTPUT_DIR, 'taxonomy_mapping.csv'))
Rows: 444 Columns: 8── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (6): species_name, birdlife_common_name, ebird_common_name, ebird_species_fullname, ebird_species_name, jetz_species_name
dbl (2): birdlife_id, ebird_id
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Find sample size, and percentage of checklists (or percentage of
effort) for valid urban communities based on presence test data.
Load abundance data
This data contains the mean and median abundance across three
different seasons for each city (breeding, nonbreeding, resident). The
data comes from the eBird status and trends data sets and is calculated
from all the pixels that occur within a city polygon for each
species.
abundance_data = read_csv(filename(EBIRD_WORKING_OUTPUT_DIR, 'ebird_trends_abundance_test_data.csv'))
Rows: 30876 Columns: 13── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (2): species, city_name
dbl (11): ID, mean_breeding, median_breeding, sd_breeding, mean_nonbreeding, median_nonbreeding, sd_nonbreeding, mean_resident, median_resident, sd_resident, ...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(abundance_data)
These are the species available for our test
test_species = unique(abundance_data$species)
test_species
[1] "Geopelia humeralis" "Patagioenas fasciata" "Macropygia phasianella" "Patagioenas araucana" "Columbina passerina"
[6] "Columba palumbus" "Ocyphaps lophotes" "Zenaida auriculata" "Streptopelia decaocto" "Columbina inca"
[11] "Zenaida macroura" "Hemiphaga novaeseelandiae" "Streptopelia orientalis" "Patagioenas picazuro" "Columbina picui"
[16] "Patagioenas flavirostris" "Streptopelia tranquebarica" "Streptopelia semitorquata" "Streptopelia capicola" "Columba livia"
[21] "Columbina talpacoti" "Patagioenas squamosa" "Patagioenas nigrirostris" "Columba guinea" "Spilopelia chinensis"
[26] "Zenaida meloda" "Patagioenas leucocephala" "Leptotila verreauxi" "Zenaida asiatica" "Treron phoenicopterus"
[31] "Zenaida aurita"
Check how these species are mapped from eBird to Jetz
test_species_mapping = unique(taxonomic_mapping[taxonomic_mapping$ebird_species_name %in% test_species, c('ebird_species_name', 'jetz_species_name')])
test_species_mapping[order(test_species_mapping$ebird_species_name),] %>% arrange(jetz_species_name)
Map abundance to jetz
abundance_data_jetz = abundance_data %>% left_join(test_species_mapping, by=c('species' = 'ebird_species_name')) %>% dplyr::select(-c('species'))
abundance_data_jetz
Load pool data
This data is created from Birdlife (and some eBird) distrbution data,
a species is recorded as occuring in a regional pool when its range
polygon intersects with a city polygon.
A set of city names we will use for inspecting data:
inspection_cities = c('Manchester', 'Bogota', 'Los Angeles', 'Jakarta', 'Nairobi', 'Medellín', 'San Jose')
Load data
pool_data = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'jetz_all_recorded_species.csv'))
Rows: 6359 Columns: 12── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (5): city_name, jetz_species_name, seasonal, presence, origin
dbl (7): city_id, total_city_checklists, total_city_effort, total_presence_checklists, total_presence_effort, percentage_checklists, percentage_effort
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
pool_data
Map taxonomy to eBird
regional_pool_data = pool_data %>%
filter(jetz_species_name %in% test_species_mapping$jetz_species_name) %>%
left_join(test_species_mapping) %>%
arrange(city_id, jetz_species_name)
Joining, by = "jetz_species_name"
Final regional pool data
regional_pool_data[regional_pool_data$city_name %in% inspection_cities,]
Filter abundance data to regional pools
The abundance data includes a record for every species in every city,
even though many of those values will be 0 or NA. Here we filter out
those erroneous rows by joining the abundance data to our regional pool
data. This will ensure we only have test data (an abundance score) for
species that we expect to occur within a city.
abundance_data_regional = left_join(regional_pool_data, abundance_data_jetz, by = c('city_id' = 'city_id', 'city_name' = 'city_name', 'jetz_species_name' = 'jetz_species_name'))
abundance_data_regional = abundance_data_regional %>% arrange(city_id) %>%
dplyr::select(-c('ID'))
Our new abundance data:
abundance_data_regional[abundance_data_regional$city_name %in% inspection_cities,]
Are any species now missing that are abundant?
Check to see if we have filtered out any species that should cause us
concern. This list contains all species that have a non-zero abundance
but have now been removed from our data (e.g. they do not occur within
the regional pool of the city, or more specifically their birdlife/ebird
range does not overlap the city vector). We can fix this by seeking
alternative ways of defining the regional pools, e.g. by appending the
eBird status and trends ranges for a species.
city_id_species_pairs_present = paste(abundance_data_regional$jetz_species_name, abundance_data_regional$city_id, sep = '::')
abundance_data_jetz %>%
filter(mean_breeding > 0 | mean_nonbreeding > 0 | mean_resident > 0) %>%
group_by(city_id, jetz_species_name) %>%
filter(!(paste(jetz_species_name, city_id, sep = '::') %in% city_id_species_pairs_present)) %>%
dplyr::select(city_id, city_name, jetz_species_name, mean_breeding, mean_nonbreeding, mean_resident)
Plot our species pools for each inspection city
plot_city_species = function(city_id) {
city_row = abundance_data_regional[abundance_data_regional$city_id == city_id, c('city_name', 'total_city_checklists')]
ggplot(abundance_data_regional[abundance_data_regional$city_id == city_id,], aes(x = jetz_species_name, y = percentage_checklists, fill = seasonal)) +
geom_bar(stat = "identity") +
geom_text(aes(label = paste('breeding', round(mean_breeding, 1), '\nresident', round(mean_resident, 1)))) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) +
xlab('Species name') + ylab('% of Checklist present') +
facet_wrap(~city_id) +
geom_hline(yintercept=5, linetype='dotted', col = 'red') +
labs(title = paste(city_row$city_name, city_row$total_city_checklists))
}
Here we show the city and total number of checklists. Then for each
species plotted against percentage of checklists, the breeding and
resident abundance.
plot_city_species(14)

plot_city_species(624)

plot_city_species(1786)

plot_city_species(4830)

plot_city_species(11920)

Predict abundance from percentage of checklists present
Each species is recorded on checklists, within each city we have
found the percentage of all checklists within a city that a species is
recorded on. A city with a low number of total checklists is likely to
have eratic and unreliable values for species percentage of checklists.
Here we try to find out what is the minimum number of checklists for a
city to have, for percentage of total checklists to map to
abundance.
Woodpigeon breeding abundance
ggplot(abundance_data_regional[abundance_data_regional$jetz_species_name == 'Columba_palumbus',], aes(y = percentage_checklists, x = mean_breeding, color = log(total_city_checklists))) + geom_jitter() + geom_jitter() + scale_colour_gradient2(
low = "red",
mid = "yellow",
high = "darkgreen",
midpoint = log(100),
space = "Lab",
na.value = "grey50",
guide = "colourbar",
aesthetics = "colour",
) +
xlab('Mean breeding abundance') + ylab('Percentage of city checklists') +
labs(title = 'Woodpigeon data across all cities', color = 'Log of total\nnumber of\ncity checklists')

Breeding abundance
ggplot(abundance_data_regional, aes(y = percentage_checklists, x = sqrt(mean_breeding), color = log(total_city_checklists))) + geom_jitter() + geom_jitter() + scale_colour_gradient2(
low = "red",
mid = "yellow",
high = "darkgreen",
midpoint = log(100),
space = "Lab",
na.value = "grey50",
guide = "colourbar",
aesthetics = "colour",
) + geom_smooth() +
xlab('Sqrt of Mean breeding abundance') + ylab('Percentage of city checklists') +
labs(title = 'All species data across all cities', color = 'Log of total\nnumber of\ncity checklists')

Outliers, where sqrt of breeding abundance greater than 3.5
abundance_data_regional[!is.na(abundance_data_regional$mean_breeding) & sqrt(abundance_data_regional$mean_breeding) > 3.5,]
Resident abundance
ggplot(abundance_data_regional, aes(y = percentage_checklists, x = sqrt(mean_resident), color = log(total_city_checklists))) + geom_jitter() + geom_jitter() + scale_colour_gradient2(
low = "red",
mid = "yellow",
high = "darkgreen",
midpoint = log(100),
space = "Lab",
na.value = "grey50",
guide = "colourbar",
aesthetics = "colour",
) + geom_smooth() +
xlab('Sqrt of Mean resident abundance') + ylab('Percentage of city checklists') +
labs(title = 'All species data across all cities', color = 'Log of total\nnumber of\ncity checklists')

Outliers, where sqrt of resident abundance greater than 5
abundance_data_regional[!is.na(abundance_data_regional$mean_resident) & sqrt(abundance_data_regional$mean_resident) > 5,]
Create max_abundance parameter
max_abundance is the maximum of breeding, nonbreeding, or resident
abundance.
Plot that against percentage of checklists, remove outliers where
sqrt of max_abundance greater than 5.
ggplot(abundance_data_regional[!is.na(abundance_data_regional$max_mean_abundance) & sqrt(abundance_data_regional$max_mean_abundance) < 5,], aes(y = percentage_checklists, x = sqrt(max_mean_abundance), color = log(total_city_checklists))) + geom_jitter() + geom_jitter() + scale_colour_gradient2(
low = "red",
mid = "yellow",
high = "darkgreen",
midpoint = log(100),
space = "Lab",
na.value = "grey50",
guide = "colourbar",
aesthetics = "colour",
) + geom_smooth() +
xlab('Sqrt of Max mean abundance') + ylab('Percentage of city checklists') +
labs(title = 'All species data across all cities', subtitle='Sqrt of max mean abundance < 5', color = 'Log of total\nnumber of\ncity checklists')

Create trimmed dataset, removing all species with a sqrt of
max_abundance greater than 5.
trimmed_data = abundance_data_regional[!is.na(abundance_data_regional$max_mean_abundance) & sqrt(abundance_data_regional$max_mean_abundance) < 5,]
Test predicting abundance directly.
Here we try to predict abundance from the percentage of checklists a
species occurs on. We try to find a cut off for the minimum total number
of checklists recorded in a city to improve the prediction. For each
test we split the cities 50/50 into training and test data, and run the
test 20 times for each cut off.
seeds = c(123, 456, 678, 10, 11, 345, 32, 11, 54, 90, 9999, 1234, 5678, 2323, 9011, 532, 111, 678, 6501, 3)
# min_city_checklists is the minimum number of cities a checklist must have to be included.
# get_model is a function that takes a dataframe subset of `abundance_data_regional`, and returns the result of `lm`
# get_actual is a function that returns the expected result of the prediction given a dataframe subset of `abundance_data_regional`
get_mean_test_error = function(min_city_checklists, get_model, get_expected) {
data = trimmed_data[trimmed_data$total_city_checklists >= min_city_checklists,]
result = data.frame()
for(seed in seeds) {
set.seed(seed)
train = sample(1:nrow(data), nrow(data)/2)
test=(-train)
model = get_model(data[train,])
prediction = predict(model, data[test,])
result = rbind(result, data.frame(prediction = prediction, actual = get_expected(data[test,])))
}
sum((result$prediction - result$actual)^2) / nrow(result)
}
min_city_checklists = seq(0, 5000, by=10)
Predict abundance directly
result_abundance = data.frame()
for (min_city_checklist in min_city_checklists) {
result_abundance = rbind(result_abundance, data.frame(
min_city_checklist = min_city_checklist,
mean_test_error = get_mean_test_error(
min_city_checklist,
function(training_data) {lm(max_mean_abundance ~ percentage_checklists, training_data)},
function(test_data) {test_data$max_mean_abundance})
))
}
ggplot(result_abundance, aes(x = min_city_checklist, y = mean_test_error)) + geom_line() +
xlab('Minimum total number of city checklists') + ylab('Mean test error for all 20 tests') +
labs(title = 'Predicting max abundance of a species in a city from the percentage of checklists')

Predict square root of abundance
Here we repeat the above test, but instead try to predict the sqrt of
max abundance.
result_sqrt_abundance = data.frame()
for (min_city_checklist in min_city_checklists) {
result_sqrt_abundance = rbind(result_sqrt_abundance, data.frame(
min_city_checklist = min_city_checklist,
mean_test_error = get_mean_test_error(
min_city_checklist,
function(training_data) {lm(sqrt(max_mean_abundance) ~ percentage_checklists, training_data)},
function(test_data) {sqrt(test_data$max_mean_abundance)})
))
}
ggplot(result_sqrt_abundance, aes(x = min_city_checklist, y = mean_test_error)) + geom_line() +
xlab('Minimum total number of city checklists') + ylab('Mean test error for all 20 tests') +
labs(title = 'Predicting sqrt max abundance from the percentage of checklists')

Test predicting the presence.
Here we try a simpler test of whether a species is present or not.
Set present as having whether a species has a max_mean_abundance greater
than 0
trimmed_data$nonzero_abundance = trimmed_data$max_mean_abundance > 0
Bin data into 25 bins based on number of total city checklists.
Boxplot percentage of checklists based on whether species are present
(i.e. max_mean_abundance greater than 0)
trimmed_data$total_city_checklists_bin = cut(trimmed_data$total_city_checklists, breaks = 25, labels = F)
trimmed_data$total_city_checklists_bin = as.factor(trimmed_data$total_city_checklists_bin)
ggplot(trimmed_data, aes(x = total_city_checklists_bin, y = percentage_checklists)) + geom_boxplot() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + facet_wrap(~ nonzero_abundance) + xlab('Total city checklists group') + ylab('Percentage of city checklists') +
labs(title = 'Boxplot of percentage of city checklists\ngiven whether the species is considered present')

Here we can see that there is a prediction problem with species
considered present that have a very low percentage of recorded
checklists.
get_true_false_test_results = function(min_city_checklists, df = trimmed_data) {
data = df[df$total_city_checklists >= min_city_checklists,]
result = data.frame()
coeeffs = c()
for(seed in seeds) {
train = sample(1:nrow(data), nrow(data) / 2)
test=(-train)
model = glm(nonzero_abundance ~ percentage_checklists, "binomial", data[train,])
prediction = predict(model, data[test,], type = "response")
result = rbind(result, data.frame(actual = data$nonzero_abundance[test], predicted = prediction))
coeeffs = append(coeeffs, model$coefficients['percentage_checklists'])
}
data.frame(
mean_false_probability = mean(result$predicted[!result$actual]),
mean_true_probablility = mean(result$predicted[result$actual]),
percentage_checklists_cutoff = mean(coeeffs),
min_city_checklists = min_city_checklists
)
}
Try predicting species presence (i.e. max_mean_abundance > 0)
based on the percentage of checklists a species is recorded on. For each
run we predict the result in our test data, and then find the mean
prediction for true vs false.
ggplot(result_present, aes(x = min_city_checklists, y = mean_false_probability)) + geom_line() +
xlab('Min total city checklists') + ylab('Mean presence probablity, given species absent')

Some results at different minimum total city checklists, the test
estimate gives us a percentage of checklists cut off for whether a
species is present. However, we can see that the mean probabilty for
present remains high for species that are absent.
result_present[result_present$min_city_checklists %in% c(100, 500, 1000, 1500, 2000),]
Which rows are causing prediction problems?
Take a look at those records that are expected present, but have low
percentage of checklists recorded. Is there any pattern here?
Any particular species?
Any particular geographical regions?
Reading layer `WB_countries_Admin0_10m' from data source `/Users/james/Dropbox/PhD/WorldBank_countries_Admin0_10m/WB_countries_Admin0_10m.shp' using driver `ESRI Shapefile'
Simple feature collection with 251 features and 52 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -180 ymin: -59.47275 xmax: 180 ymax: 83.6341
Geodetic CRS: WGS 84
Reading layer `initial_selection' from data source `/Users/james/Dropbox/PhD/urban_community_structure_wrk/geo/cities/initial_selection.shp' using driver `ESRI Shapefile'
Simple feature collection with 996 features and 4 fields
Geometry type: POLYGON
Dimension: XY
Bounding box: xmin: -158.043 ymin: -38.20685 xmax: 174.9891 ymax: 60.3276
Geodetic CRS: WGS 84
Joining, by = c("city_name", "city_id")`summarise()` has grouped output by 'city_id', 'city_name'. You can override using the `.groups` argument.

Try excluding particular problem species
Exclude
- Streptopelia orientalis
- Leptotila verreauxi
- Streptopelia decaocto
- Streptopelia tranquebarica
problem_species_to_exclude = c('Streptopelia_orientalis', 'Leptotila_verreauxi', 'Streptopelia_decaocto', 'Streptopelia_tranquebarica')
ggplot(result_present_exclude_species, aes(x = min_city_checklists, y = mean_false_probability)) + geom_line() +
xlab('Min total city checklists') + ylab('Mean presence probablity, given species absent')

result_present_exclude_species[result_present_exclude_species$min_city_checklists %in% c(100, 500, 1000, 1500, 2000),]
Try excluding particular regions of the world
Exclude cities in Southern Asia and Central America
cities_joined_to_world = st_join(initial_city_selection, world_map)
although coordinates are longitude/latitude, st_intersects assumes that they are planar
problem_cities_to_exclude = cities_joined_to_world$city_id[cities_joined_to_world$SUBREGION %in% c('Southern Asia', 'Central America')]
ggplot(result_present_exclude_cities, aes(x = min_city_checklists, y = mean_false_probability)) + geom_line() +
xlab('Min total city checklists') + ylab('Mean presence probablity, given species absent')

result_present_exclude_cities[result_present_exclude_cities$min_city_checklists %in% c(100, 500, 1000, 1500, 2000),]
min(result_present_exclude_cities$min_city_checklists[result_present_exclude_cities$mean_false_probability < 0.5])
[1] 350
round(result_present_exclude_cities$percentage_checklists_cutoff[result_present_exclude_cities$min_city_checklists == 350], 1)
[1] 3.6
In Summary
If we’re happy that we can exclude South American and South Asian
cities due to poor eBird sampling data leading to their models producing
invalid abundance. Then selecting the first test that produces an
average true probability under 0.5 for absent species, means we should
set the minimum required number of checklists within a city to 380, and
the percentage cut-off for a species being present as 3.6%.
The number of cities in the analysis is then:
length(unique(trimmed_data$city_id[trimmed_data$total_city_checklists >= 350]))
[1] 297
Distribution of those cities

LS0tCnRpdGxlOiAiRmluZCBwYXJhbWV0ZXJzIGZvciBidWlsZGluZyB1cmJhbiBjb21tdW5pdGllcyIKb3V0cHV0OiBodG1sX25vdGVib29rCmJpYmxpb2dyYXBoeTogLi4vcmVmLmJpYiAKLS0tCgpVc2luZyBlQmlyZCB0cmVuZHMgYW5kIHN0YXR1cyBhYnVuZGFuY2UgZGF0YSwgY2FuIHdlIGZpbmQgYSBtaW5pbXVtIG51bWJlciBvZiBjaXR5IHRvdGFsIGNoZWNrbGlzdHMgcmVxdWlyZWQsIGFuZCBhIGN1dCBvZmYgcGVyY2VudGFnZSBvZiBjaGVja2xpc3RzIHJlY29yZGVkLCBzdWNoIHRoYXQgd2UgY2FuIGJlIGNvbmZpZGVudCB0aGF0IGFueSBzcGVjaWVzIHJlY29yZGVkIG9uIG1vcmUgdGhhbiB0aGF0IHBlcmNlbnRhZ2Ugb2YgY2hlY2tsaXN0IGlzIHByZXNlbnQgaW4gdGhlIGNpdHkgKGdpdmVuIHRoZSBjaXR5IGhhcyBtb3JlIHRoYXQgdGhlIHRvdGFsIG1pbmltdW0gdG90YWwgY2hlY2tsaXN0cyByZWNvcmRlZCB3aXRoaW4gaXQpLgoKYGBge3J9CnNvdXJjZSgnLi4vZW52LlInKQpvcHRpb25zKHdhcm49LTEpCmBgYAoKYGBge3J9CnRheG9ub21pY19tYXBwaW5nID0gcmVhZF9jc3YoZmlsZW5hbWUoVEFYT05PTVlfT1VUUFVUX0RJUiwgJ3RheG9ub215X21hcHBpbmcuY3N2JykpCmBgYAoKRmluZCBzYW1wbGUgc2l6ZSwgYW5kIHBlcmNlbnRhZ2Ugb2YgY2hlY2tsaXN0cyAob3IgcGVyY2VudGFnZSBvZiBlZmZvcnQpIGZvciB2YWxpZCB1cmJhbiBjb21tdW5pdGllcyBiYXNlZCBvbiBwcmVzZW5jZSB0ZXN0IGRhdGEuCgojIExvYWQgYWJ1bmRhbmNlIGRhdGEKVGhpcyBkYXRhIGNvbnRhaW5zIHRoZSBtZWFuIGFuZCBtZWRpYW4gYWJ1bmRhbmNlIGFjcm9zcyB0aHJlZSBkaWZmZXJlbnQgc2Vhc29ucyBmb3IgZWFjaCBjaXR5IChicmVlZGluZywgbm9uYnJlZWRpbmcsIHJlc2lkZW50KS4KVGhlIGRhdGEgY29tZXMgZnJvbSB0aGUgZUJpcmQgc3RhdHVzIGFuZCB0cmVuZHMgZGF0YSBzZXRzIGFuZCBpcyBjYWxjdWxhdGVkIGZyb20gYWxsIHRoZSBwaXhlbHMgdGhhdCBvY2N1ciB3aXRoaW4gYSBjaXR5IHBvbHlnb24gZm9yIGVhY2ggc3BlY2llcy4KYGBge3J9CmFidW5kYW5jZV9kYXRhID0gcmVhZF9jc3YoZmlsZW5hbWUoRUJJUkRfV09SS0lOR19PVVRQVVRfRElSLCAnZWJpcmRfdHJlbmRzX2FidW5kYW5jZV90ZXN0X2RhdGEuY3N2JykpCmhlYWQoYWJ1bmRhbmNlX2RhdGEpCmBgYApUaGVzZSBhcmUgdGhlIHNwZWNpZXMgYXZhaWxhYmxlIGZvciBvdXIgdGVzdApgYGB7cn0KdGVzdF9zcGVjaWVzID0gdW5pcXVlKGFidW5kYW5jZV9kYXRhJHNwZWNpZXMpCnRlc3Rfc3BlY2llcwpgYGAKCiMjIENoZWNrIGhvdyB0aGVzZSBzcGVjaWVzIGFyZSBtYXBwZWQgZnJvbSBlQmlyZCB0byBKZXR6CmBgYHtyfQp0ZXN0X3NwZWNpZXNfbWFwcGluZyA9IHVuaXF1ZSh0YXhvbm9taWNfbWFwcGluZ1t0YXhvbm9taWNfbWFwcGluZyRlYmlyZF9zcGVjaWVzX25hbWUgJWluJSB0ZXN0X3NwZWNpZXMsIGMoJ2ViaXJkX3NwZWNpZXNfbmFtZScsICdqZXR6X3NwZWNpZXNfbmFtZScpXSkKCnRlc3Rfc3BlY2llc19tYXBwaW5nW29yZGVyKHRlc3Rfc3BlY2llc19tYXBwaW5nJGViaXJkX3NwZWNpZXNfbmFtZSksXSAlPiUgYXJyYW5nZShqZXR6X3NwZWNpZXNfbmFtZSkKYGBgCgojIyBNYXAgYWJ1bmRhbmNlIHRvIGpldHoKYGBge3J9CmFidW5kYW5jZV9kYXRhX2pldHogPSBhYnVuZGFuY2VfZGF0YSAlPiUgbGVmdF9qb2luKHRlc3Rfc3BlY2llc19tYXBwaW5nLCBieT1jKCdzcGVjaWVzJyA9ICdlYmlyZF9zcGVjaWVzX25hbWUnKSkgJT4lIGRwbHlyOjpzZWxlY3QoLWMoJ3NwZWNpZXMnKSkKYWJ1bmRhbmNlX2RhdGFfamV0egpgYGAKCiMgTG9hZCBwb29sIGRhdGEKVGhpcyBkYXRhIGlzIGNyZWF0ZWQgZnJvbSBCaXJkbGlmZSAoYW5kIHNvbWUgZUJpcmQpIGRpc3RyYnV0aW9uIGRhdGEsIGEgc3BlY2llcyBpcyByZWNvcmRlZCBhcyBvY2N1cmluZyBpbiBhIHJlZ2lvbmFsIHBvb2wgd2hlbiBpdHMgcmFuZ2UgcG9seWdvbiBpbnRlcnNlY3RzIHdpdGggYSBjaXR5IHBvbHlnb24uCgpBIHNldCBvZiBjaXR5IG5hbWVzIHdlIHdpbGwgdXNlIGZvciBpbnNwZWN0aW5nIGRhdGE6CmBgYHtyfQppbnNwZWN0aW9uX2NpdGllcyA9IGMoJ01hbmNoZXN0ZXInLCAnQm9nb3RhJywgJ0xvcyBBbmdlbGVzJywgJ0pha2FydGEnLCAnTmFpcm9iaScsICdNZWRlbGzDrW4nLCAnU2FuIEpvc2UnKQpgYGAKCkxvYWQgZGF0YQpgYGB7cn0KcG9vbF9kYXRhID0gcmVhZF9jc3YoZmlsZW5hbWUoQ09NTVVOSVRZX09VVFBVVF9ESVIsICdqZXR6X2FsbF9yZWNvcmRlZF9zcGVjaWVzLmNzdicpKQpwb29sX2RhdGEKYGBgCk1hcCB0YXhvbm9teSB0byBlQmlyZApgYGB7cn0KcmVnaW9uYWxfcG9vbF9kYXRhID0gcG9vbF9kYXRhICU+JSAKICBmaWx0ZXIoamV0el9zcGVjaWVzX25hbWUgJWluJSB0ZXN0X3NwZWNpZXNfbWFwcGluZyRqZXR6X3NwZWNpZXNfbmFtZSkgJT4lCiAgbGVmdF9qb2luKHRlc3Rfc3BlY2llc19tYXBwaW5nKSAlPiUKICBhcnJhbmdlKGNpdHlfaWQsIGpldHpfc3BlY2llc19uYW1lKQpgYGAKCkZpbmFsIHJlZ2lvbmFsIHBvb2wgZGF0YQpgYGB7cn0KcmVnaW9uYWxfcG9vbF9kYXRhW3JlZ2lvbmFsX3Bvb2xfZGF0YSRjaXR5X25hbWUgJWluJSBpbnNwZWN0aW9uX2NpdGllcyxdCmBgYAoKIyBGaWx0ZXIgYWJ1bmRhbmNlIGRhdGEgdG8gcmVnaW9uYWwgcG9vbHMKVGhlIGFidW5kYW5jZSBkYXRhIGluY2x1ZGVzIGEgcmVjb3JkIGZvciBldmVyeSBzcGVjaWVzIGluIGV2ZXJ5IGNpdHksIGV2ZW4gdGhvdWdoIG1hbnkgb2YgdGhvc2UgdmFsdWVzIHdpbGwgYmUgMCBvciBOQS4KSGVyZSB3ZSBmaWx0ZXIgb3V0IHRob3NlIGVycm9uZW91cyByb3dzIGJ5IGpvaW5pbmcgdGhlIGFidW5kYW5jZSBkYXRhIHRvIG91ciByZWdpb25hbCBwb29sIGRhdGEuIFRoaXMgd2lsbCBlbnN1cmUgd2Ugb25seSBoYXZlIHRlc3QgZGF0YSAoYW4gYWJ1bmRhbmNlIHNjb3JlKSBmb3Igc3BlY2llcyB0aGF0IHdlIGV4cGVjdCB0byBvY2N1ciB3aXRoaW4gYSBjaXR5LgpgYGB7cn0KYWJ1bmRhbmNlX2RhdGFfcmVnaW9uYWwgPSBsZWZ0X2pvaW4ocmVnaW9uYWxfcG9vbF9kYXRhLCBhYnVuZGFuY2VfZGF0YV9qZXR6LCBieSA9IGMoJ2NpdHlfaWQnID0gJ2NpdHlfaWQnLCAnY2l0eV9uYW1lJyA9ICdjaXR5X25hbWUnLCAnamV0el9zcGVjaWVzX25hbWUnID0gJ2pldHpfc3BlY2llc19uYW1lJykpCgphYnVuZGFuY2VfZGF0YV9yZWdpb25hbCA9IGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsICU+JSBhcnJhbmdlKGNpdHlfaWQpICU+JQogIGRwbHlyOjpzZWxlY3QoLWMoJ0lEJykpCmBgYAoKT3VyIG5ldyBhYnVuZGFuY2UgZGF0YToKYGBge3J9CmFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsW2FidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJGNpdHlfbmFtZSAlaW4lIGluc3BlY3Rpb25fY2l0aWVzLF0KYGBgCgojIyBBcmUgYW55IHNwZWNpZXMgbm93IG1pc3NpbmcgdGhhdCBhcmUgYWJ1bmRhbnQ/CkNoZWNrIHRvIHNlZSBpZiB3ZSBoYXZlIGZpbHRlcmVkIG91dCBhbnkgc3BlY2llcyB0aGF0IHNob3VsZCBjYXVzZSB1cyBjb25jZXJuLgpUaGlzIGxpc3QgY29udGFpbnMgYWxsIHNwZWNpZXMgdGhhdCBoYXZlIGEgbm9uLXplcm8gYWJ1bmRhbmNlIGJ1dCBoYXZlIG5vdyBiZWVuIHJlbW92ZWQgZnJvbSBvdXIgZGF0YSAoZS5nLiB0aGV5IGRvIG5vdCBvY2N1ciB3aXRoaW4gdGhlIHJlZ2lvbmFsIHBvb2wgb2YgdGhlIGNpdHksIG9yIG1vcmUgc3BlY2lmaWNhbGx5IHRoZWlyIGJpcmRsaWZlL2ViaXJkIHJhbmdlIGRvZXMgbm90IG92ZXJsYXAgdGhlIGNpdHkgdmVjdG9yKS4gV2UgY2FuIGZpeCB0aGlzIGJ5IHNlZWtpbmcgYWx0ZXJuYXRpdmUgd2F5cyBvZiBkZWZpbmluZyB0aGUgcmVnaW9uYWwgcG9vbHMsIGUuZy4gYnkgYXBwZW5kaW5nIHRoZSBlQmlyZCBzdGF0dXMgYW5kIHRyZW5kcyByYW5nZXMgZm9yIGEgc3BlY2llcy4KYGBge3J9CmNpdHlfaWRfc3BlY2llc19wYWlyc19wcmVzZW50ID0gcGFzdGUoYWJ1bmRhbmNlX2RhdGFfcmVnaW9uYWwkamV0el9zcGVjaWVzX25hbWUsIGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJGNpdHlfaWQsIHNlcCA9ICc6OicpCmFidW5kYW5jZV9kYXRhX2pldHogJT4lIAogIGZpbHRlcihtZWFuX2JyZWVkaW5nID4gMCB8IG1lYW5fbm9uYnJlZWRpbmcgPiAwIHwgbWVhbl9yZXNpZGVudCA+IDApICU+JSAKICBncm91cF9ieShjaXR5X2lkLCBqZXR6X3NwZWNpZXNfbmFtZSkgJT4lIAogIGZpbHRlcighKHBhc3RlKGpldHpfc3BlY2llc19uYW1lLCBjaXR5X2lkLCBzZXAgPSAnOjonKSAlaW4lIGNpdHlfaWRfc3BlY2llc19wYWlyc19wcmVzZW50KSkgJT4lCiAgZHBseXI6OnNlbGVjdChjaXR5X2lkLCBjaXR5X25hbWUsIGpldHpfc3BlY2llc19uYW1lLCBtZWFuX2JyZWVkaW5nLCBtZWFuX25vbmJyZWVkaW5nLCBtZWFuX3Jlc2lkZW50KQpgYGAKCgojIFBsb3Qgb3VyIHNwZWNpZXMgcG9vbHMgZm9yIGVhY2ggaW5zcGVjdGlvbiBjaXR5CmBgYHtyfQpwbG90X2NpdHlfc3BlY2llcyA9IGZ1bmN0aW9uKGNpdHlfaWQpIHsKICBjaXR5X3JvdyA9ICBhYnVuZGFuY2VfZGF0YV9yZWdpb25hbFthYnVuZGFuY2VfZGF0YV9yZWdpb25hbCRjaXR5X2lkID09IGNpdHlfaWQsIGMoJ2NpdHlfbmFtZScsICd0b3RhbF9jaXR5X2NoZWNrbGlzdHMnKV0KCiAgZ2dwbG90KGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsW2FidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJGNpdHlfaWQgPT0gY2l0eV9pZCxdLCBhZXMoeCA9IGpldHpfc3BlY2llc19uYW1lLCB5ID0gcGVyY2VudGFnZV9jaGVja2xpc3RzLCBmaWxsID0gc2Vhc29uYWwpKSArIAogICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUoJ2JyZWVkaW5nJywgcm91bmQobWVhbl9icmVlZGluZywgMSksICdcbnJlc2lkZW50Jywgcm91bmQobWVhbl9yZXNpZGVudCwgMSkpKSkgKyAKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkgKyAKICAgIHhsYWIoJ1NwZWNpZXMgbmFtZScpICsgeWxhYignJSBvZiBDaGVja2xpc3QgcHJlc2VudCcpICsgCiAgICBmYWNldF93cmFwKH5jaXR5X2lkKSArIAogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTUsIGxpbmV0eXBlPSdkb3R0ZWQnLCBjb2wgPSAncmVkJykgKwogICAgbGFicyh0aXRsZSA9IHBhc3RlKGNpdHlfcm93JGNpdHlfbmFtZSwgY2l0eV9yb3ckdG90YWxfY2l0eV9jaGVja2xpc3RzKSkKfQpgYGAKCkhlcmUgd2Ugc2hvdyB0aGUgY2l0eSBhbmQgdG90YWwgbnVtYmVyIG9mIGNoZWNrbGlzdHMuIFRoZW4gZm9yIGVhY2ggc3BlY2llcyBwbG90dGVkIGFnYWluc3QgcGVyY2VudGFnZSBvZiBjaGVja2xpc3RzLCB0aGUgYnJlZWRpbmcgYW5kIHJlc2lkZW50IGFidW5kYW5jZS4KYGBge3J9CnBsb3RfY2l0eV9zcGVjaWVzKDE0KQpgYGAKCmBgYHtyfQpwbG90X2NpdHlfc3BlY2llcyg2MjQpCmBgYAoKYGBge3J9CnBsb3RfY2l0eV9zcGVjaWVzKDE3ODYpCmBgYAoKYGBge3J9CnBsb3RfY2l0eV9zcGVjaWVzKDQ4MzApCmBgYAoKYGBge3J9CnBsb3RfY2l0eV9zcGVjaWVzKDExOTIwKQpgYGAKCiMgUHJlZGljdCBhYnVuZGFuY2UgZnJvbSBwZXJjZW50YWdlIG9mIGNoZWNrbGlzdHMgcHJlc2VudApFYWNoIHNwZWNpZXMgaXMgcmVjb3JkZWQgb24gY2hlY2tsaXN0cywgd2l0aGluIGVhY2ggY2l0eSB3ZSBoYXZlIGZvdW5kIHRoZSBwZXJjZW50YWdlIG9mIGFsbCBjaGVja2xpc3RzIHdpdGhpbiBhIGNpdHkgdGhhdCBhIHNwZWNpZXMgaXMgcmVjb3JkZWQgb24uCkEgY2l0eSB3aXRoIGEgbG93IG51bWJlciBvZiB0b3RhbCBjaGVja2xpc3RzIGlzIGxpa2VseSB0byBoYXZlIGVyYXRpYyBhbmQgdW5yZWxpYWJsZSB2YWx1ZXMgZm9yIHNwZWNpZXMgcGVyY2VudGFnZSBvZiBjaGVja2xpc3RzLgpIZXJlIHdlIHRyeSB0byBmaW5kIG91dCB3aGF0IGlzIHRoZSBtaW5pbXVtIG51bWJlciBvZiBjaGVja2xpc3RzIGZvciBhIGNpdHkgdG8gaGF2ZSwgZm9yIHBlcmNlbnRhZ2Ugb2YgdG90YWwgY2hlY2tsaXN0cyB0byBtYXAgdG8gYWJ1bmRhbmNlLgoKIyMgV29vZHBpZ2VvbiBicmVlZGluZyBhYnVuZGFuY2UKYGBge3J9CmdncGxvdChhYnVuZGFuY2VfZGF0YV9yZWdpb25hbFthYnVuZGFuY2VfZGF0YV9yZWdpb25hbCRqZXR6X3NwZWNpZXNfbmFtZSA9PSAnQ29sdW1iYV9wYWx1bWJ1cycsXSwgYWVzKHkgPSBwZXJjZW50YWdlX2NoZWNrbGlzdHMsIHggPSBtZWFuX2JyZWVkaW5nLCBjb2xvciA9IGxvZyh0b3RhbF9jaXR5X2NoZWNrbGlzdHMpKSkgKyBnZW9tX2ppdHRlcigpICsgZ2VvbV9qaXR0ZXIoKSArIHNjYWxlX2NvbG91cl9ncmFkaWVudDIoCiAgbG93ID0gInJlZCIsCiAgbWlkID0gInllbGxvdyIsCiAgaGlnaCA9ICJkYXJrZ3JlZW4iLAogIG1pZHBvaW50ID0gbG9nKDEwMCksCiAgc3BhY2UgPSAiTGFiIiwKICBuYS52YWx1ZSA9ICJncmV5NTAiLAogIGd1aWRlID0gImNvbG91cmJhciIsCiAgYWVzdGhldGljcyA9ICJjb2xvdXIiLAopICsgCnhsYWIoJ01lYW4gYnJlZWRpbmcgYWJ1bmRhbmNlJykgKyB5bGFiKCdQZXJjZW50YWdlIG9mIGNpdHkgY2hlY2tsaXN0cycpICsgCmxhYnModGl0bGUgPSAnV29vZHBpZ2VvbiBkYXRhIGFjcm9zcyBhbGwgY2l0aWVzJywgY29sb3IgPSAnTG9nIG9mIHRvdGFsXG5udW1iZXIgb2ZcbmNpdHkgY2hlY2tsaXN0cycpCmBgYAojIyBCcmVlZGluZyBhYnVuZGFuY2UKYGBge3J9CmdncGxvdChhYnVuZGFuY2VfZGF0YV9yZWdpb25hbCwgYWVzKHkgPSBwZXJjZW50YWdlX2NoZWNrbGlzdHMsIHggPSBzcXJ0KG1lYW5fYnJlZWRpbmcpLCBjb2xvciA9IGxvZyh0b3RhbF9jaXR5X2NoZWNrbGlzdHMpKSkgKyBnZW9tX2ppdHRlcigpICsgZ2VvbV9qaXR0ZXIoKSArIHNjYWxlX2NvbG91cl9ncmFkaWVudDIoCiAgbG93ID0gInJlZCIsCiAgbWlkID0gInllbGxvdyIsCiAgaGlnaCA9ICJkYXJrZ3JlZW4iLAogIG1pZHBvaW50ID0gbG9nKDEwMCksCiAgc3BhY2UgPSAiTGFiIiwKICBuYS52YWx1ZSA9ICJncmV5NTAiLAogIGd1aWRlID0gImNvbG91cmJhciIsCiAgYWVzdGhldGljcyA9ICJjb2xvdXIiLAopICsgZ2VvbV9zbW9vdGgoKSArIAp4bGFiKCdTcXJ0IG9mIE1lYW4gYnJlZWRpbmcgYWJ1bmRhbmNlJykgKyB5bGFiKCdQZXJjZW50YWdlIG9mIGNpdHkgY2hlY2tsaXN0cycpICsgCmxhYnModGl0bGUgPSAnQWxsIHNwZWNpZXMgZGF0YSBhY3Jvc3MgYWxsIGNpdGllcycsIGNvbG9yID0gJ0xvZyBvZiB0b3RhbFxubnVtYmVyIG9mXG5jaXR5IGNoZWNrbGlzdHMnKQpgYGAKCk91dGxpZXJzLCB3aGVyZSBzcXJ0IG9mIGJyZWVkaW5nIGFidW5kYW5jZSBncmVhdGVyIHRoYW4gMy41CmBgYHtyfQphYnVuZGFuY2VfZGF0YV9yZWdpb25hbFshaXMubmEoYWJ1bmRhbmNlX2RhdGFfcmVnaW9uYWwkbWVhbl9icmVlZGluZykgJiBzcXJ0KGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJG1lYW5fYnJlZWRpbmcpID4gMy41LF0KYGBgCgojIyBSZXNpZGVudCBhYnVuZGFuY2UKYGBge3J9CmdncGxvdChhYnVuZGFuY2VfZGF0YV9yZWdpb25hbCwgYWVzKHkgPSBwZXJjZW50YWdlX2NoZWNrbGlzdHMsIHggPSBzcXJ0KG1lYW5fcmVzaWRlbnQpLCBjb2xvciA9IGxvZyh0b3RhbF9jaXR5X2NoZWNrbGlzdHMpKSkgKyBnZW9tX2ppdHRlcigpICsgZ2VvbV9qaXR0ZXIoKSArIHNjYWxlX2NvbG91cl9ncmFkaWVudDIoCiAgbG93ID0gInJlZCIsCiAgbWlkID0gInllbGxvdyIsCiAgaGlnaCA9ICJkYXJrZ3JlZW4iLAogIG1pZHBvaW50ID0gbG9nKDEwMCksCiAgc3BhY2UgPSAiTGFiIiwKICBuYS52YWx1ZSA9ICJncmV5NTAiLAogIGd1aWRlID0gImNvbG91cmJhciIsCiAgYWVzdGhldGljcyA9ICJjb2xvdXIiLAopICsgZ2VvbV9zbW9vdGgoKSArIAp4bGFiKCdTcXJ0IG9mIE1lYW4gcmVzaWRlbnQgYWJ1bmRhbmNlJykgKyB5bGFiKCdQZXJjZW50YWdlIG9mIGNpdHkgY2hlY2tsaXN0cycpICsgCmxhYnModGl0bGUgPSAnQWxsIHNwZWNpZXMgZGF0YSBhY3Jvc3MgYWxsIGNpdGllcycsIGNvbG9yID0gJ0xvZyBvZiB0b3RhbFxubnVtYmVyIG9mXG5jaXR5IGNoZWNrbGlzdHMnKQpgYGAKCk91dGxpZXJzLCB3aGVyZSBzcXJ0IG9mIHJlc2lkZW50IGFidW5kYW5jZSBncmVhdGVyIHRoYW4gNQpgYGB7cn0KYWJ1bmRhbmNlX2RhdGFfcmVnaW9uYWxbIWlzLm5hKGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJG1lYW5fcmVzaWRlbnQpICYgc3FydChhYnVuZGFuY2VfZGF0YV9yZWdpb25hbCRtZWFuX3Jlc2lkZW50KSA+IDUsXQpgYGAKCiMjIENyZWF0ZSBtYXhfYWJ1bmRhbmNlIHBhcmFtZXRlcgptYXhfYWJ1bmRhbmNlIGlzIHRoZSBtYXhpbXVtIG9mIGJyZWVkaW5nLCBub25icmVlZGluZywgb3IgcmVzaWRlbnQgYWJ1bmRhbmNlLgpgYGB7ciwgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJG1heF9tZWFuX2FidW5kYW5jZSA9IGFwcGx5KGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsWyxjKCdtZWFuX2JyZWVkaW5nJywgJ21lYW5fbm9uYnJlZWRpbmcnLCAnbWVhbl9yZXNpZGVudCcpXSwgMSwgbWF4LCBuYS5ybSA9IFQpCmFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJG1heF9tZWFuX2FidW5kYW5jZVthYnVuZGFuY2VfZGF0YV9yZWdpb25hbCRtYXhfbWVhbl9hYnVuZGFuY2UgPT0gLUluZl0gPSBOQQpgYGAKClBsb3QgdGhhdCBhZ2FpbnN0IHBlcmNlbnRhZ2Ugb2YgY2hlY2tsaXN0cywgcmVtb3ZlIG91dGxpZXJzIHdoZXJlIHNxcnQgb2YgbWF4X2FidW5kYW5jZSBncmVhdGVyIHRoYW4gNS4KYGBge3J9CmdncGxvdChhYnVuZGFuY2VfZGF0YV9yZWdpb25hbFshaXMubmEoYWJ1bmRhbmNlX2RhdGFfcmVnaW9uYWwkbWF4X21lYW5fYWJ1bmRhbmNlKSAmIHNxcnQoYWJ1bmRhbmNlX2RhdGFfcmVnaW9uYWwkbWF4X21lYW5fYWJ1bmRhbmNlKSA8IDUsXSwgYWVzKHkgPSBwZXJjZW50YWdlX2NoZWNrbGlzdHMsIHggPSBzcXJ0KG1heF9tZWFuX2FidW5kYW5jZSksIGNvbG9yID0gbG9nKHRvdGFsX2NpdHlfY2hlY2tsaXN0cykpKSArIGdlb21faml0dGVyKCkgKyBnZW9tX2ppdHRlcigpICsgc2NhbGVfY29sb3VyX2dyYWRpZW50MigKICBsb3cgPSAicmVkIiwKICBtaWQgPSAieWVsbG93IiwKICBoaWdoID0gImRhcmtncmVlbiIsCiAgbWlkcG9pbnQgPSBsb2coMTAwKSwKICBzcGFjZSA9ICJMYWIiLAogIG5hLnZhbHVlID0gImdyZXk1MCIsCiAgZ3VpZGUgPSAiY29sb3VyYmFyIiwKICBhZXN0aGV0aWNzID0gImNvbG91ciIsCikgKyBnZW9tX3Ntb290aCgpICsgCnhsYWIoJ1NxcnQgb2YgTWF4IG1lYW4gYWJ1bmRhbmNlJykgKyB5bGFiKCdQZXJjZW50YWdlIG9mIGNpdHkgY2hlY2tsaXN0cycpICsgCmxhYnModGl0bGUgPSAnQWxsIHNwZWNpZXMgZGF0YSBhY3Jvc3MgYWxsIGNpdGllcycsIHN1YnRpdGxlPSdTcXJ0IG9mIG1heCBtZWFuIGFidW5kYW5jZSA8IDUnLCBjb2xvciA9ICdMb2cgb2YgdG90YWxcbm51bWJlciBvZlxuY2l0eSBjaGVja2xpc3RzJykKYGBgCgpDcmVhdGUgdHJpbW1lZCBkYXRhc2V0LCByZW1vdmluZyBhbGwgc3BlY2llcyB3aXRoIGEgc3FydCBvZiBtYXhfYWJ1bmRhbmNlIGdyZWF0ZXIgdGhhbiA1LgpgYGB7cn0KdHJpbW1lZF9kYXRhID0gYWJ1bmRhbmNlX2RhdGFfcmVnaW9uYWxbIWlzLm5hKGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJG1heF9tZWFuX2FidW5kYW5jZSkgJiBzcXJ0KGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsJG1heF9tZWFuX2FidW5kYW5jZSkgPCA1LF0KYGBgCgojIyBUZXN0IHByZWRpY3RpbmcgYWJ1bmRhbmNlIGRpcmVjdGx5LgpIZXJlIHdlIHRyeSB0byBwcmVkaWN0IGFidW5kYW5jZSBmcm9tIHRoZSBwZXJjZW50YWdlIG9mIGNoZWNrbGlzdHMgYSBzcGVjaWVzIG9jY3VycyBvbi4KV2UgdHJ5IHRvIGZpbmQgYSBjdXQgb2ZmIGZvciB0aGUgbWluaW11bSB0b3RhbCBudW1iZXIgb2YgY2hlY2tsaXN0cyByZWNvcmRlZCBpbiBhIGNpdHkgdG8gaW1wcm92ZSB0aGUgcHJlZGljdGlvbi4KRm9yIGVhY2ggdGVzdCB3ZSBzcGxpdCB0aGUgY2l0aWVzIDUwLzUwIGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSwgYW5kIHJ1biB0aGUgdGVzdCAyMCB0aW1lcyBmb3IgZWFjaCBjdXQgb2ZmLgoKYGBge3J9CnNlZWRzID0gYygxMjMsIDQ1NiwgNjc4LCAxMCwgMTEsIDM0NSwgMzIsIDExLCA1NCwgOTAsIDk5OTksIDEyMzQsIDU2NzgsIDIzMjMsIDkwMTEsIDUzMiwgMTExLCA2NzgsIDY1MDEsIDMpCgojIG1pbl9jaXR5X2NoZWNrbGlzdHMgaXMgdGhlIG1pbmltdW0gbnVtYmVyIG9mIGNpdGllcyBhIGNoZWNrbGlzdCBtdXN0IGhhdmUgdG8gYmUgaW5jbHVkZWQuCiMgZ2V0X21vZGVsIGlzIGEgZnVuY3Rpb24gdGhhdCB0YWtlcyBhIGRhdGFmcmFtZSBzdWJzZXQgb2YgYGFidW5kYW5jZV9kYXRhX3JlZ2lvbmFsYCwgYW5kIHJldHVybnMgdGhlIHJlc3VsdCBvZiBgbG1gCiMgZ2V0X2FjdHVhbCBpcyBhIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyB0aGUgZXhwZWN0ZWQgcmVzdWx0IG9mIHRoZSBwcmVkaWN0aW9uIGdpdmVuIGEgZGF0YWZyYW1lIHN1YnNldCBvZiBgYWJ1bmRhbmNlX2RhdGFfcmVnaW9uYWxgCmdldF9tZWFuX3Rlc3RfZXJyb3IgPSBmdW5jdGlvbihtaW5fY2l0eV9jaGVja2xpc3RzLCBnZXRfbW9kZWwsIGdldF9leHBlY3RlZCkgewogIGRhdGEgPSB0cmltbWVkX2RhdGFbdHJpbW1lZF9kYXRhJHRvdGFsX2NpdHlfY2hlY2tsaXN0cyA+PSBtaW5fY2l0eV9jaGVja2xpc3RzLF0KICAKICByZXN1bHQgPSBkYXRhLmZyYW1lKCkKICAKICBmb3Ioc2VlZCBpbiBzZWVkcykgewogICAgc2V0LnNlZWQoc2VlZCkKICAgIHRyYWluID0gc2FtcGxlKDE6bnJvdyhkYXRhKSwgbnJvdyhkYXRhKS8yKQogICAgdGVzdD0oLXRyYWluKQogICAgCiAgICBtb2RlbCA9IGdldF9tb2RlbChkYXRhW3RyYWluLF0pCiAgICBwcmVkaWN0aW9uID0gcHJlZGljdChtb2RlbCwgZGF0YVt0ZXN0LF0pCiAgICAKICAgIHJlc3VsdCA9IHJiaW5kKHJlc3VsdCwgZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gcHJlZGljdGlvbiwgYWN0dWFsID0gZ2V0X2V4cGVjdGVkKGRhdGFbdGVzdCxdKSkpCiAgfQoKICBzdW0oKHJlc3VsdCRwcmVkaWN0aW9uIC0gcmVzdWx0JGFjdHVhbCleMikgLyBucm93KHJlc3VsdCkKfQoKbWluX2NpdHlfY2hlY2tsaXN0cyA9IHNlcSgwLCA1MDAwLCBieT0xMCkKYGBgCgojIyMgUHJlZGljdCBhYnVuZGFuY2UgZGlyZWN0bHkKYGBge3J9CnJlc3VsdF9hYnVuZGFuY2UgPSBkYXRhLmZyYW1lKCkKCmZvciAobWluX2NpdHlfY2hlY2tsaXN0IGluIG1pbl9jaXR5X2NoZWNrbGlzdHMpIHsKICByZXN1bHRfYWJ1bmRhbmNlID0gcmJpbmQocmVzdWx0X2FidW5kYW5jZSwgZGF0YS5mcmFtZSgKICAgIG1pbl9jaXR5X2NoZWNrbGlzdCA9IG1pbl9jaXR5X2NoZWNrbGlzdCwKICAgIG1lYW5fdGVzdF9lcnJvciA9IGdldF9tZWFuX3Rlc3RfZXJyb3IoCiAgICAgIG1pbl9jaXR5X2NoZWNrbGlzdCwgCiAgICAgIGZ1bmN0aW9uKHRyYWluaW5nX2RhdGEpIHtsbShtYXhfbWVhbl9hYnVuZGFuY2UgfiBwZXJjZW50YWdlX2NoZWNrbGlzdHMsIHRyYWluaW5nX2RhdGEpfSwKICAgICAgZnVuY3Rpb24odGVzdF9kYXRhKSB7dGVzdF9kYXRhJG1heF9tZWFuX2FidW5kYW5jZX0pCiAgKSkKfQoKZ2dwbG90KHJlc3VsdF9hYnVuZGFuY2UsIGFlcyh4ID0gbWluX2NpdHlfY2hlY2tsaXN0LCB5ID0gbWVhbl90ZXN0X2Vycm9yKSkgKyBnZW9tX2xpbmUoKSArCiAgeGxhYignTWluaW11bSB0b3RhbCBudW1iZXIgb2YgY2l0eSBjaGVja2xpc3RzJykgKyB5bGFiKCdNZWFuIHRlc3QgZXJyb3IgZm9yIGFsbCAyMCB0ZXN0cycpICsKICBsYWJzKHRpdGxlID0gJ1ByZWRpY3RpbmcgbWF4IGFidW5kYW5jZSBvZiBhIHNwZWNpZXMgaW4gYSBjaXR5IGZyb20gdGhlIHBlcmNlbnRhZ2Ugb2YgY2hlY2tsaXN0cycpCmBgYAoKIyMjIFByZWRpY3Qgc3F1YXJlIHJvb3Qgb2YgYWJ1bmRhbmNlCkhlcmUgd2UgcmVwZWF0IHRoZSBhYm92ZSB0ZXN0LCBidXQgaW5zdGVhZCB0cnkgdG8gcHJlZGljdCB0aGUgc3FydCBvZiBtYXggYWJ1bmRhbmNlLgpgYGB7cn0KcmVzdWx0X3NxcnRfYWJ1bmRhbmNlID0gZGF0YS5mcmFtZSgpCgpmb3IgKG1pbl9jaXR5X2NoZWNrbGlzdCBpbiBtaW5fY2l0eV9jaGVja2xpc3RzKSB7CiAgcmVzdWx0X3NxcnRfYWJ1bmRhbmNlID0gcmJpbmQocmVzdWx0X3NxcnRfYWJ1bmRhbmNlLCBkYXRhLmZyYW1lKAogICAgbWluX2NpdHlfY2hlY2tsaXN0ID0gbWluX2NpdHlfY2hlY2tsaXN0LAogICAgbWVhbl90ZXN0X2Vycm9yID0gZ2V0X21lYW5fdGVzdF9lcnJvcigKICAgICAgbWluX2NpdHlfY2hlY2tsaXN0LCAKICAgICAgZnVuY3Rpb24odHJhaW5pbmdfZGF0YSkge2xtKHNxcnQobWF4X21lYW5fYWJ1bmRhbmNlKSB+IHBlcmNlbnRhZ2VfY2hlY2tsaXN0cywgdHJhaW5pbmdfZGF0YSl9LAogICAgICBmdW5jdGlvbih0ZXN0X2RhdGEpIHtzcXJ0KHRlc3RfZGF0YSRtYXhfbWVhbl9hYnVuZGFuY2UpfSkKICApKQp9CgpnZ3Bsb3QocmVzdWx0X3NxcnRfYWJ1bmRhbmNlLCBhZXMoeCA9IG1pbl9jaXR5X2NoZWNrbGlzdCwgeSA9IG1lYW5fdGVzdF9lcnJvcikpICsgZ2VvbV9saW5lKCkgKwogIHhsYWIoJ01pbmltdW0gdG90YWwgbnVtYmVyIG9mIGNpdHkgY2hlY2tsaXN0cycpICsgeWxhYignTWVhbiB0ZXN0IGVycm9yIGZvciBhbGwgMjAgdGVzdHMnKSArCiAgbGFicyh0aXRsZSA9ICdQcmVkaWN0aW5nIHNxcnQgbWF4IGFidW5kYW5jZSBmcm9tIHRoZSBwZXJjZW50YWdlIG9mIGNoZWNrbGlzdHMnKQpgYGAKCiMjIFRlc3QgcHJlZGljdGluZyB0aGUgcHJlc2VuY2UuCkhlcmUgd2UgdHJ5IGEgc2ltcGxlciB0ZXN0IG9mIHdoZXRoZXIgYSBzcGVjaWVzIGlzIHByZXNlbnQgb3Igbm90LgpTZXQgcHJlc2VudCBhcyBoYXZpbmcgd2hldGhlciBhIHNwZWNpZXMgaGFzIGEgbWF4X21lYW5fYWJ1bmRhbmNlIGdyZWF0ZXIgdGhhbiAwCgpgYGB7cn0KdHJpbW1lZF9kYXRhJG5vbnplcm9fYWJ1bmRhbmNlID0gdHJpbW1lZF9kYXRhJG1heF9tZWFuX2FidW5kYW5jZSA+IDAKYGBgCgpCaW4gZGF0YSBpbnRvIDI1IGJpbnMgYmFzZWQgb24gbnVtYmVyIG9mIHRvdGFsIGNpdHkgY2hlY2tsaXN0cy4KQm94cGxvdCBwZXJjZW50YWdlIG9mIGNoZWNrbGlzdHMgYmFzZWQgb24gd2hldGhlciBzcGVjaWVzIGFyZSBwcmVzZW50IChpLmUuIG1heF9tZWFuX2FidW5kYW5jZSBncmVhdGVyIHRoYW4gMCkKYGBge3J9CnRyaW1tZWRfZGF0YSR0b3RhbF9jaXR5X2NoZWNrbGlzdHNfYmluID0gY3V0KHRyaW1tZWRfZGF0YSR0b3RhbF9jaXR5X2NoZWNrbGlzdHMsIGJyZWFrcyA9IDI1LCBsYWJlbHMgPSBGKQp0cmltbWVkX2RhdGEkdG90YWxfY2l0eV9jaGVja2xpc3RzX2JpbiA9IGFzLmZhY3Rvcih0cmltbWVkX2RhdGEkdG90YWxfY2l0eV9jaGVja2xpc3RzX2JpbikKZ2dwbG90KHRyaW1tZWRfZGF0YSwgYWVzKHggPSB0b3RhbF9jaXR5X2NoZWNrbGlzdHNfYmluLCB5ID0gcGVyY2VudGFnZV9jaGVja2xpc3RzKSkgKyBnZW9tX2JveHBsb3QoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkgKyBmYWNldF93cmFwKH4gbm9uemVyb19hYnVuZGFuY2UpICsgeGxhYignVG90YWwgY2l0eSBjaGVja2xpc3RzIGdyb3VwJykgKyB5bGFiKCdQZXJjZW50YWdlIG9mIGNpdHkgY2hlY2tsaXN0cycpICsgCmxhYnModGl0bGUgPSAnQm94cGxvdCBvZiBwZXJjZW50YWdlIG9mIGNpdHkgY2hlY2tsaXN0c1xuZ2l2ZW4gd2hldGhlciB0aGUgc3BlY2llcyBpcyBjb25zaWRlcmVkIHByZXNlbnQnKQpgYGAKSGVyZSB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgaXMgYSBwcmVkaWN0aW9uIHByb2JsZW0gd2l0aCBzcGVjaWVzIGNvbnNpZGVyZWQgcHJlc2VudCB0aGF0IGhhdmUgYSB2ZXJ5IGxvdyBwZXJjZW50YWdlIG9mIHJlY29yZGVkIGNoZWNrbGlzdHMuCgpgYGB7cn0KZ2V0X3RydWVfZmFsc2VfdGVzdF9yZXN1bHRzID0gZnVuY3Rpb24obWluX2NpdHlfY2hlY2tsaXN0cywgZGYgPSB0cmltbWVkX2RhdGEpIHsKICBkYXRhID0gZGZbZGYkdG90YWxfY2l0eV9jaGVja2xpc3RzID49IG1pbl9jaXR5X2NoZWNrbGlzdHMsXQogIAogIHJlc3VsdCA9IGRhdGEuZnJhbWUoKQogIGNvZWVmZnMgPSBjKCkKICAKICBmb3Ioc2VlZCBpbiBzZWVkcykgewogICAgdHJhaW4gPSBzYW1wbGUoMTpucm93KGRhdGEpLCBucm93KGRhdGEpIC8gMikKICAgIHRlc3Q9KC10cmFpbikKICAgIAogICAgbW9kZWwgPSBnbG0obm9uemVyb19hYnVuZGFuY2UgfiBwZXJjZW50YWdlX2NoZWNrbGlzdHMsICJiaW5vbWlhbCIsIGRhdGFbdHJhaW4sXSkKICAgIHByZWRpY3Rpb24gPSBwcmVkaWN0KG1vZGVsLCBkYXRhW3Rlc3QsXSwgdHlwZSA9ICJyZXNwb25zZSIpCiAgICAKICAgIHJlc3VsdCA9IHJiaW5kKHJlc3VsdCwgZGF0YS5mcmFtZShhY3R1YWwgPSBkYXRhJG5vbnplcm9fYWJ1bmRhbmNlW3Rlc3RdLCBwcmVkaWN0ZWQgPSBwcmVkaWN0aW9uKSkKICAgIGNvZWVmZnMgPSBhcHBlbmQoY29lZWZmcywgbW9kZWwkY29lZmZpY2llbnRzWydwZXJjZW50YWdlX2NoZWNrbGlzdHMnXSkKICB9CgogIGRhdGEuZnJhbWUoCiAgICBtZWFuX2ZhbHNlX3Byb2JhYmlsaXR5ID0gbWVhbihyZXN1bHQkcHJlZGljdGVkWyFyZXN1bHQkYWN0dWFsXSksCiAgICBtZWFuX3RydWVfcHJvYmFibGlsaXR5ID0gbWVhbihyZXN1bHQkcHJlZGljdGVkW3Jlc3VsdCRhY3R1YWxdKSwKICAgIHBlcmNlbnRhZ2VfY2hlY2tsaXN0c19jdXRvZmYgPSBtZWFuKGNvZWVmZnMpLAogICAgbWluX2NpdHlfY2hlY2tsaXN0cyA9IG1pbl9jaXR5X2NoZWNrbGlzdHMKICApCn0KYGBgCgoKVHJ5IHByZWRpY3Rpbmcgc3BlY2llcyBwcmVzZW5jZSAoaS5lLiBtYXhfbWVhbl9hYnVuZGFuY2UgPiAwKSBiYXNlZCBvbiB0aGUgcGVyY2VudGFnZSBvZiBjaGVja2xpc3RzIGEgc3BlY2llcyBpcyByZWNvcmRlZCBvbi4KRm9yIGVhY2ggcnVuIHdlIHByZWRpY3QgdGhlIHJlc3VsdCBpbiBvdXIgdGVzdCBkYXRhLCBhbmQgdGhlbiBmaW5kIHRoZSBtZWFuIHByZWRpY3Rpb24gZm9yIHRydWUgdnMgZmFsc2UuCmBgYHtyLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyZXN1bHRfcHJlc2VudCA9IGRhdGEuZnJhbWUoKQoKZm9yIChtaW5fY2l0eV9jaGVja2xpc3QgaW4gbWluX2NpdHlfY2hlY2tsaXN0cykgewogIHJlc3VsdF9wcmVzZW50ID0gcmJpbmQocmVzdWx0X3ByZXNlbnQsIGdldF90cnVlX2ZhbHNlX3Rlc3RfcmVzdWx0cyhtaW5fY2l0eV9jaGVja2xpc3QpKQp9CmBgYAoKYGBge3J9CmdncGxvdChyZXN1bHRfcHJlc2VudCwgYWVzKHggPSBtaW5fY2l0eV9jaGVja2xpc3RzLCB5ID0gbWVhbl9mYWxzZV9wcm9iYWJpbGl0eSkpICsgZ2VvbV9saW5lKCkgKwogIHhsYWIoJ01pbiB0b3RhbCBjaXR5IGNoZWNrbGlzdHMnKSArIHlsYWIoJ01lYW4gcHJlc2VuY2UgcHJvYmFibGl0eSwgZ2l2ZW4gc3BlY2llcyBhYnNlbnQnKQpgYGAKClNvbWUgcmVzdWx0cyBhdCBkaWZmZXJlbnQgbWluaW11bSB0b3RhbCBjaXR5IGNoZWNrbGlzdHMsIHRoZSB0ZXN0IGVzdGltYXRlIGdpdmVzIHVzIGEgcGVyY2VudGFnZSBvZiBjaGVja2xpc3RzIGN1dCBvZmYgZm9yIHdoZXRoZXIgYSBzcGVjaWVzIGlzIHByZXNlbnQuCkhvd2V2ZXIsIHdlIGNhbiBzZWUgdGhhdCB0aGUgbWVhbiBwcm9iYWJpbHR5IGZvciBwcmVzZW50IHJlbWFpbnMgaGlnaCBmb3Igc3BlY2llcyB0aGF0IGFyZSBhYnNlbnQuCmBgYHtyfQpyZXN1bHRfcHJlc2VudFtyZXN1bHRfcHJlc2VudCRtaW5fY2l0eV9jaGVja2xpc3RzICVpbiUgYygxMDAsIDUwMCwgMTAwMCwgMTUwMCwgMjAwMCksXQpgYGAKIyMjIFdoaWNoIHJvd3MgYXJlIGNhdXNpbmcgcHJlZGljdGlvbiBwcm9ibGVtcz8KVGFrZSBhIGxvb2sgYXQgdGhvc2UgcmVjb3JkcyB0aGF0IGFyZSBleHBlY3RlZCBwcmVzZW50LCBidXQgaGF2ZSBsb3cgcGVyY2VudGFnZSBvZiBjaGVja2xpc3RzIHJlY29yZGVkLiBJcyB0aGVyZSBhbnkgcGF0dGVybiBoZXJlPwpgYGB7cn0KcHJvYmxlbV9yb3dzID0gdHJpbW1lZF9kYXRhW3RyaW1tZWRfZGF0YSRub256ZXJvX2FidW5kYW5jZSAmIHRyaW1tZWRfZGF0YSRwZXJjZW50YWdlX2NoZWNrbGlzdHMgPCAyLjUgJiB0cmltbWVkX2RhdGEkdG90YWxfY2l0eV9jaGVja2xpc3RzID4gMTAwLAogICAgICAgICAgICAgYygnY2l0eV9uYW1lJywgJ2pldHpfc3BlY2llc19uYW1lJywgJ3RvdGFsX2NpdHlfY2hlY2tsaXN0cycsICdtZWFuX2JyZWVkaW5nJywgJ21lZGlhbl9icmVlZGluZycsICdtZWFuX3Jlc2lkZW50JywgJ21lZGlhbl9yZXNpZGVudCcsICdtZWFuX25vbmJyZWVkaW5nJywgJ21lZGlhbl9ub25icmVlZGluZycsICdzZWFzb25hbCcsICdwZXJjZW50YWdlX2NoZWNrbGlzdHMnLCAnY2l0eV9pZCcpXQpwcm9ibGVtX3Jvd3MKYGBgCkFueSBwYXJ0aWN1bGFyIHNwZWNpZXM/CmBgYHtyfQpwcm9ibGVtX3Jvd3MgJT4lIGdyb3VwX2J5KGpldHpfc3BlY2llc19uYW1lKSAlPiUgc3VtbWFyaXNlKAogIHRvdGFsX3Byb2JsZW1fcmVjb3JkcyA9IG4oKSwgCiAgbWVhbl9tZWFuX2JyZWVkaW5nID0gbWVhbihtZWFuX2JyZWVkaW5nKSwgCiAgbWF4X21lYW5fYnJlZWRpbmcgPSBtYXgobWVhbl9icmVlZGluZyksIAogIG1lYW5fbWVkaWFuX2JyZWVkaW5nID0gbWVhbihtZWRpYW5fYnJlZWRpbmcpLCAKICBtYXhfbWVkaWFuX2JyZWVkaW5nID0gbWF4KG1lZGlhbl9icmVlZGluZyksIAogIG1lYW5fbWVhbl9ub25icmVlZGluZyA9IG1lYW4obWVhbl9ub25icmVlZGluZyksIAogIG1heF9tZWFuX25vbmJyZWVkaW5nID0gbWF4KG1lYW5fbm9uYnJlZWRpbmcpLCAKICBtZWFuX21lZGlhbl9ub25icmVlZGluZyA9IG1lYW4obWVkaWFuX25vbmJyZWVkaW5nKSwgCiAgbWF4X21lZGlhbl9ub25icmVlZGluZyA9IG1heChtZWRpYW5fbm9uYnJlZWRpbmcpLCAKICBtZWFuX21lYW5fcmVzaWRlbnQgPSBtZWFuKG1lYW5fcmVzaWRlbnQpLCAKICBtYXhfbWVhbl9yZXNpZGVudCA9IG1heChtZWFuX3Jlc2lkZW50KSwgCiAgbWVhbl9tZWRpYW5fcmVzaWRlbnQgPSBtZWFuKG1lZGlhbl9yZXNpZGVudCksIAogIG1heF9tZWRpYW5fcmVzaWRlbnQgPSBtYXgobWVkaWFuX3Jlc2lkZW50KQopICU+JSBhcnJhbmdlKHRvdGFsX3Byb2JsZW1fcmVjb3JkcykKYGBgCgpBbnkgcGFydGljdWxhciBnZW9ncmFwaGljYWwgcmVnaW9ucz8KYGBge3IsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNmOjpzZl91c2VfczIoRkFMU0UpCkNPVU5UUllfQk9VTkRBUklFUyA9ICcvVXNlcnMvamFtZXMvRHJvcGJveC9QaEQvV29ybGRCYW5rX2NvdW50cmllc19BZG1pbjBfMTBtL1dCX2NvdW50cmllc19BZG1pbjBfMTBtLnNocCcKd29ybGRfbWFwID0gc3Rfc2ltcGxpZnkoc3RfcmVhZChDT1VOVFJZX0JPVU5EQVJJRVMpLCBkVG9sZXJhbmNlID0gMC4wMikKCmluaXRpYWxfY2l0eV9zZWxlY3Rpb24gPSBzdF9yZWFkKGZpbGVuYW1lKG1rZGlyKEdFT19XT1JLSU5HX09VVFBVVF9ESVIsICdjaXRpZXMnKSwgJ2luaXRpYWxfc2VsZWN0aW9uLnNocCcpKQoKcHJvYmxlbV9yZWdpb25zID0gcHJvYmxlbV9yb3dzICU+JQogIGxlZnRfam9pbihpbml0aWFsX2NpdHlfc2VsZWN0aW9uKSAlPiUKICBncm91cF9ieShjaXR5X2lkLCBjaXR5X25hbWUpICU+JQogIHN1bW1hcmlzZSh0b3RhbF9wcm9ibGVtX3JlY29yZHMgPSBuKCksIGdlb20gPSBzdF9jZW50cm9pZChnZW9tZXRyeSkpCgpnZ3Bsb3QoKSArICAKICBnZW9tX3NmKGRhdGEgPSB3b3JsZF9tYXAsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKwogIGdlb21fc2YoZGF0YSA9IHByb2JsZW1fcmVnaW9ucywgYWVzKGdlb21ldHJ5ID0gZ2VvbSwgY29sb3VyID0gdG90YWxfcHJvYmxlbV9yZWNvcmRzKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsgc2NhbGVfY29sb3VyX2dyYWRpZW50MigKICAgIGxvdyA9ICJkYXJrZ3JlZW4iLAogICAgbWlkID0gInllbGxvdyIsCiAgICBoaWdoID0gInJlZCIsCiAgICBtaWRwb2ludCA9IDMsCiAgICBzcGFjZSA9ICJMYWIiLAogICAgbmEudmFsdWUgPSAiZ3JleTUwIiwKICAgIGd1aWRlID0gImNvbG91cmJhciIsCiAgICBhZXN0aGV0aWNzID0gImNvbG91ciIsCiAgKQpgYGAKIyMjIFRyeSBleGNsdWRpbmcgcGFydGljdWxhciBwcm9ibGVtIHNwZWNpZXMKRXhjbHVkZQoKKiBTdHJlcHRvcGVsaWEgb3JpZW50YWxpcwoqIExlcHRvdGlsYSB2ZXJyZWF1eGkKKiBTdHJlcHRvcGVsaWEgZGVjYW9jdG8KKiBTdHJlcHRvcGVsaWEgdHJhbnF1ZWJhcmljYQoKYGBge3J9CnByb2JsZW1fc3BlY2llc190b19leGNsdWRlID0gYygnU3RyZXB0b3BlbGlhX29yaWVudGFsaXMnLCAnTGVwdG90aWxhX3ZlcnJlYXV4aScsICdTdHJlcHRvcGVsaWFfZGVjYW9jdG8nLCAnU3RyZXB0b3BlbGlhX3RyYW5xdWViYXJpY2EnKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0cmltbWVkX2RhdGFfd2l0aF9leGNsdWRlZF9zcGVjaWVzID0gdHJpbW1lZF9kYXRhWyEodHJpbW1lZF9kYXRhJGpldHpfc3BlY2llc19uYW1lICVpbiUgcHJvYmxlbV9zcGVjaWVzX3RvX2V4Y2x1ZGUpLF0KCnJlc3VsdF9wcmVzZW50X2V4Y2x1ZGVfc3BlY2llcyA9IGRhdGEuZnJhbWUoKQoKZm9yIChtaW5fY2l0eV9jaGVja2xpc3QgaW4gbWluX2NpdHlfY2hlY2tsaXN0cykgewogIHJlc3VsdF9wcmVzZW50X2V4Y2x1ZGVfc3BlY2llcyA9IHJiaW5kKHJlc3VsdF9wcmVzZW50X2V4Y2x1ZGVfc3BlY2llcywgZ2V0X3RydWVfZmFsc2VfdGVzdF9yZXN1bHRzKG1pbl9jaXR5X2NoZWNrbGlzdCwgZGYgPSB0cmltbWVkX2RhdGFfd2l0aF9leGNsdWRlZF9zcGVjaWVzKSkKfQpgYGAKCmBgYHtyfQpnZ3Bsb3QocmVzdWx0X3ByZXNlbnRfZXhjbHVkZV9zcGVjaWVzLCBhZXMoeCA9IG1pbl9jaXR5X2NoZWNrbGlzdHMsIHkgPSBtZWFuX2ZhbHNlX3Byb2JhYmlsaXR5KSkgKyBnZW9tX2xpbmUoKSArCiAgeGxhYignTWluIHRvdGFsIGNpdHkgY2hlY2tsaXN0cycpICsgeWxhYignTWVhbiBwcmVzZW5jZSBwcm9iYWJsaXR5LCBnaXZlbiBzcGVjaWVzIGFic2VudCcpCmBgYAoKYGBge3J9CnJlc3VsdF9wcmVzZW50X2V4Y2x1ZGVfc3BlY2llc1tyZXN1bHRfcHJlc2VudF9leGNsdWRlX3NwZWNpZXMkbWluX2NpdHlfY2hlY2tsaXN0cyAlaW4lIGMoMTAwLCA1MDAsIDEwMDAsIDE1MDAsIDIwMDApLF0KYGBgCgoKIyMjIFRyeSBleGNsdWRpbmcgcGFydGljdWxhciByZWdpb25zIG9mIHRoZSB3b3JsZApFeGNsdWRlIGNpdGllcyBpbiBTb3V0aGVybiBBc2lhIGFuZCBDZW50cmFsIEFtZXJpY2EKCmBgYHtyfQpjaXRpZXNfam9pbmVkX3RvX3dvcmxkID0gc3Rfam9pbihpbml0aWFsX2NpdHlfc2VsZWN0aW9uLCB3b3JsZF9tYXApCnByb2JsZW1fY2l0aWVzX3RvX2V4Y2x1ZGUgPSBjaXRpZXNfam9pbmVkX3RvX3dvcmxkJGNpdHlfaWRbY2l0aWVzX2pvaW5lZF90b193b3JsZCRTVUJSRUdJT04gJWluJSBjKCdTb3V0aGVybiBBc2lhJywgJ0NlbnRyYWwgQW1lcmljYScpXQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0cmltbWVkX2RhdGFfd2l0aF9leGNsdWRlZF9jaXRpZXMgPSB0cmltbWVkX2RhdGFbISh0cmltbWVkX2RhdGEkY2l0eV9pZCAlaW4lIHByb2JsZW1fY2l0aWVzX3RvX2V4Y2x1ZGUpLF0KCnJlc3VsdF9wcmVzZW50X2V4Y2x1ZGVfY2l0aWVzID0gZGF0YS5mcmFtZSgpCgpmb3IgKG1pbl9jaXR5X2NoZWNrbGlzdCBpbiBtaW5fY2l0eV9jaGVja2xpc3RzKSB7CiAgcmVzdWx0X3ByZXNlbnRfZXhjbHVkZV9jaXRpZXMgPSByYmluZChyZXN1bHRfcHJlc2VudF9leGNsdWRlX2NpdGllcywgZ2V0X3RydWVfZmFsc2VfdGVzdF9yZXN1bHRzKG1pbl9jaXR5X2NoZWNrbGlzdCwgZGYgPSB0cmltbWVkX2RhdGFfd2l0aF9leGNsdWRlZF9jaXRpZXMpKQp9CmBgYAoKYGBge3J9CmdncGxvdChyZXN1bHRfcHJlc2VudF9leGNsdWRlX2NpdGllcywgYWVzKHggPSBtaW5fY2l0eV9jaGVja2xpc3RzLCB5ID0gbWVhbl9mYWxzZV9wcm9iYWJpbGl0eSkpICsgZ2VvbV9saW5lKCkgKwogIHhsYWIoJ01pbiB0b3RhbCBjaXR5IGNoZWNrbGlzdHMnKSArIHlsYWIoJ01lYW4gcHJlc2VuY2UgcHJvYmFibGl0eSwgZ2l2ZW4gc3BlY2llcyBhYnNlbnQnKQpgYGAKCgpgYGB7cn0KcmVzdWx0X3ByZXNlbnRfZXhjbHVkZV9jaXRpZXNbcmVzdWx0X3ByZXNlbnRfZXhjbHVkZV9jaXRpZXMkbWluX2NpdHlfY2hlY2tsaXN0cyAlaW4lIGMoMTAwLCA1MDAsIDEwMDAsIDE1MDAsIDIwMDApLF0KYGBgCgpgYGB7cn0KbWluKHJlc3VsdF9wcmVzZW50X2V4Y2x1ZGVfY2l0aWVzJG1pbl9jaXR5X2NoZWNrbGlzdHNbcmVzdWx0X3ByZXNlbnRfZXhjbHVkZV9jaXRpZXMkbWVhbl9mYWxzZV9wcm9iYWJpbGl0eSA8IDAuNV0pCmBgYAoKYGBge3J9CnJvdW5kKHJlc3VsdF9wcmVzZW50X2V4Y2x1ZGVfY2l0aWVzJHBlcmNlbnRhZ2VfY2hlY2tsaXN0c19jdXRvZmZbcmVzdWx0X3ByZXNlbnRfZXhjbHVkZV9jaXRpZXMkbWluX2NpdHlfY2hlY2tsaXN0cyA9PSAzNTBdLCAxKQpgYGAKCiMjIyBJbiBTdW1tYXJ5CklmIHdlJ3JlIGhhcHB5IHRoYXQgd2UgY2FuIGV4Y2x1ZGUgU291dGggQW1lcmljYW4gYW5kIFNvdXRoIEFzaWFuIGNpdGllcyBkdWUgdG8gcG9vciBlQmlyZCBzYW1wbGluZyBkYXRhIGxlYWRpbmcgdG8gdGhlaXIgbW9kZWxzIHByb2R1Y2luZyBpbnZhbGlkIGFidW5kYW5jZS4gVGhlbiBzZWxlY3RpbmcgdGhlIGZpcnN0IHRlc3QgdGhhdCBwcm9kdWNlcyBhbiBhdmVyYWdlIHRydWUgcHJvYmFiaWxpdHkgdW5kZXIgMC41IGZvciBhYnNlbnQgc3BlY2llcywgbWVhbnMgd2Ugc2hvdWxkIHNldCB0aGUgbWluaW11bSByZXF1aXJlZCBudW1iZXIgb2YgY2hlY2tsaXN0cyB3aXRoaW4gYSBjaXR5IHRvIDM4MCwgYW5kIHRoZSBwZXJjZW50YWdlIGN1dC1vZmYgZm9yIGEgc3BlY2llcyBiZWluZyBwcmVzZW50IGFzIDMuNiUuCgpUaGUgbnVtYmVyIG9mIGNpdGllcyBpbiB0aGUgYW5hbHlzaXMgaXMgdGhlbjoKYGBge3J9Cmxlbmd0aCh1bmlxdWUodHJpbW1lZF9kYXRhJGNpdHlfaWRbdHJpbW1lZF9kYXRhJHRvdGFsX2NpdHlfY2hlY2tsaXN0cyA+PSAzNTBdKSkKYGBgCgpEaXN0cmlidXRpb24gb2YgdGhvc2UgY2l0aWVzCmBgYHtyfQpzZWxlY3RlZF9jaXRpZXNfZm9yX3N0dWR5ID0gdW5pcXVlKHRyaW1tZWRfZGF0YSRjaXR5X2lkW3RyaW1tZWRfZGF0YSR0b3RhbF9jaXR5X2NoZWNrbGlzdHMgPj0gMzUwXSkKc2VsZWN0ZWRfY2l0eV9jZW50cmVzID0gc3RfY2VudHJvaWQoaW5pdGlhbF9jaXR5X3NlbGVjdGlvbltpbml0aWFsX2NpdHlfc2VsZWN0aW9uJGNpdHlfaWQgJWluJSBzZWxlY3RlZF9jaXRpZXNfZm9yX3N0dWR5LF0pCgpnZ3Bsb3QoKSArICAKICBnZW9tX3NmKGRhdGEgPSB3b3JsZF9tYXAsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKwogIGdlb21fc2YoZGF0YSA9IHNlbGVjdGVkX2NpdHlfY2VudHJlcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnkpLCBjb2xvdXIgPSAicmVkIikKYGBg